trotter 1.0.1

Trotter 🎠 is an experimental crate that aims to make writing Gemini clients fun and easy.
Documentation

🎠 Trotter

Trotter is an experimental crate that aims to make interacting with gemini servers fun and easy.

This crate comes with the trot command-line program that exposes most of its functionality. Install it with the command cargo install --features cli trotter.

There's also Fluffer, a crate for writing gemini server apps.

😊 Requests

Trotter seeks to be a feature-complete gemini client library, while also being easy to use.

Trot

If you're only here to send a quick request, refer to the ergonomic [trot] and [trot_in] methods.

#[tokio::main]
async fn main() {
    trotter::trot("geminiprotocol.net") // gemini:// prefix and root slash can be elided
        .await
        .unwrap();

    trotter::trot_in("localhost/input", "notice me!")
        .await
        .unwrap();
}

Actors

For more-detailed requests, you can use an [Actor].

You can use the builder pattern to easily attach a user agent and client certificate to the actor.

Once you've built an [Actor], you can call [Actor::get] to send a request with it.

use trotter::{Actor, UserAgent};

#[tokio::main]
async fn main() {
    let owo = Actor::default()
        .user_agent(UserAgent::Indexer)
        .cert_file("id/owo.crt")
        .key_file("id/owo.key");
        
    owo.get("localhost")
        .await
        .unwrap();
}

🤖 User-agents

Did you know there's a version of the robots.txt standard for gemini? (robots.txt for Gemini)

Trotter has robots functionality built-in. Once you set your user-agent, you will receive a RobotDenied error if you try to access a page you are disallowed from.

I strongly suggest you do this if you're using Trotter for a project that depends on other peoples' content.

🌕 Titan

Titan is a sister-protocol to gemini that allows clients to upload files. (read more)

To use titan, construct a [Titan] object, and pass it to the [Actor::upload] method.

use trotter::{Actor, Titan};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let g = Actor::default()
        .upload(
            "titan://localhost/",
            Titan {
                content:  "Example content :DDDDDDDDDDDDDDDDDDDD".into(),
                mimetype: "text/plain".into(),
                token:    None,
            },
        )
        .await?
        .gemtext()?;
    println!("{g}");

    Ok(())
}

🎁 Responses

Once you receive a structured [Response], you can either weed through it yourself, or rely on the helper functions it implements to preform common operations.

📖 Parsing

Trotter also provides tools for parsing gemtext.

use trotter::parse::Gemtext;

fn main() {
    let txt = "# 💎
## Is
### So
> effing
* dope
man
=> /path/to/somewhere i can take u there
``` alt text goes here
Here's a table
| The | Best |
|-----|------|
| 😘  | 😪   |
```";

    let gemtext = Gemtext::parse(txt);

    println!("{gemtext:#?}");
}

Tips

Certificates

If you have access to a posix shell with openssl installed, you can define the following functions to easily generate and inspect x509 certificates.

certgen() { [ -f "${1:?usage: certgen [domain]}.key" ] || [ -f "$1.crt" ] || ( openssl req -new -subj "/CN=$1" -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -days 3650 -nodes -out "$1.crt" -keyout "$1.key" && printf '📜 Cert generated\n' ) ;}
certinfo(){ openssl x509 -noout -text < "${1:?usage: certinfo [file]}" ;}

Todo

For now, I want this to be a helpful tool for automating gemini requests. But ultimately, I would like for it to be robust enough to write a complete client with.

  • Write response to file
  • Get response as gemtext
  • robots.txt support
  • Custom errors
  • Cli binary 👀
  • Server certificates
  • Tofu store directory
  • Byte read/write timeout